<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <button id="btn1">外反锁</button>
    <button id="btn2">关灯</button>

    <!-- 
    ·全局共有5件小米智能家居：小米控制台、小米门禁、小米电灯、小米电视；
    ·从外反锁门=>全屋进入【外出模式】=>所有电器自动断电
    ·电灯关闭10分钟=>全屋自动进入【睡眠模式】=>电视关闭+空调进入微风状态+门禁内反锁；
    -->

    <!-- 设计模式底层架构 -->
    <script>
        /* 小米家居基类 */
        class MiDevice {
            constructor(name) {
                this.name = name
            }

            powerOn() {
                console.log(this.name, "powerOn");
            }

            powerOff() {
                console.log(this.name, "powerOff");
            }
        }

        /* 事件源=被观察者（Observable） */
        class DataSource extends MiDevice {
            constructor(name) {
                super(name)
                this.listeners = []
                this.value = null
            }

            // 添加观察者
            addListener(...listeners) {
                this.listeners.push(...listeners)
            }

            // 注销观察者
            removeListener(listener) {
                this.listeners = this.listeners.filter(
                    li => li !== listener
                )
            }

            // 发布事件（通知所有观察者响应）
            publishEvent(event) {
                this.listeners.forEach(
                    li => li.onEvent(event)
                )
            }

        }

        /* 观察者：只要有onEvent(event)方法就是观察者（无论是否显式地继承Observer） */
        class Observer extends MiDevice {
            onEvent(event) {
                console.log(this.name, "onEvent", event);
            }
        }

        /* 组合 */
        class Compose extends MiDevice {
            constructor(name) {
                super(name)
                this.components = []
            }

            // 添加组件
            addComponent(component) {
                this.components.push(component)
            }

            // 注销组件
            removeComponent(component) {
                for (let i = 0; i < this.components.length; i++) {
                    if (this.components[i] === component) {
                        this.components.splice(i, 1)
                        break
                    }
                }
            }

            // 下达号令
            platformSwitchMode(mode) {
                // 让所有组件都响应号令
                this.components.forEach(
                    c => c.switchMode(mode)
                )
            }
        }

        /* 组件: 只要有switchMode(mode)方法就可算是组件（无论是否显式地继承Component） */
        class Component extends MiDevice {
            switchMode(mode) { }
        }

    </script>

    <!-- 家居类封装 -->
    <script>
        /* 小爱同学 */
        class MiControl extends /* Observer */Compose {

            static mode = {
                modeAbsent: "外出模式",
                modeSleep: "睡眠模式"
            }

            static singleton = null
            static getSingleton() {
                !this.singleton && (this.singleton = new MiControl)
                return this.singleton
            }

            constructor() {
                super("小爱同学")
            }

            /* 作为观察者响应事件 */
            /* 我已经有onEvent了 所以我已经是观察者了 无需一定要继承Observer */
            onEvent(event) {
                // super.onEvent(event)
                console.log(this.name, "onEvent", event);

                switch (event.type) {
                    case MiDoor.eventType:
                        if (event.value === MiDoor.mode.lockFromOutside) {
                            // 命令所有组件进入【外出模式】
                            this.platformSwitchMode(MiControl.mode.modeAbsent)
                        }
                        break;

                    default:
                        break;
                }
            }
        }

        class MiDoor extends DataSource {
            static eventType = "MiDoor"

            static mode = {
                open: "open",
                closed: "closed",
                lockFromInside: "lockFromInside",
                lockFromOutside: "lockFromOutside"
            }

            constructor() {
                super("小米门禁")
            }

            handle(mode) {
                switch (mode) {
                    case MiDoor.mode.lockFromOutside:
                        console.log("门禁外反锁");
                        break;

                    default:
                        break;
                }

                /* 作为事件源主动发布事件 */
                this.publishEvent({
                    type: MiDoor.eventType,
                    value: mode
                })
            }

            switchMode(mode){
                console.log(this.name,"switchMode",mode);
            }
        }

        class MiLight extends Component {
            constructor() {
                super("MiLight")
            }

            switchMode(mode) {
                switch (mode) {
                    // 外出模式
                    case MiControl.mode.modeAbsent:
                        this.powerOff()
                        break;
                    default:
                        break;
                }
            }
        }

        class MiTV extends Component {
            constructor() {
                super("MiTV")
            }

            switchMode(mode) {
                switch (mode) {
                    // 外出模式
                    case MiControl.mode.modeAbsent:
                        this.powerOff()
                        break;
                    default:
                        break;
                }
            }
        }
    </script>

    <!-- 业务逻辑 -->
    <script>
        const mcontrol = MiControl.getSingleton()

        const mdoor = new MiDoor()
        const mlight = new MiLight()
        const mtv = new MiTV()

        // 小米门禁（事件源）添加小爱同学作为观察者
        mdoor.addListener(mcontrol)

        /* 所有设备都归小爱同学统一号令 */
        mcontrol.addComponent(mdoor)
        mcontrol.addComponent(mlight)
        mcontrol.addComponent(mtv)

        /* 反锁门 */
        btn1.onclick = function (e) {
            mdoor.handle(MiDoor.mode.lockFromOutside)
        }
    </script>

</body>

</html>